#include "fmb.h"
#include <iostream>
#include <cassert>

using namespace fm::base;
using namespace std;

void simpleFn() {
  for (int i = 0; i < 100; ++i) {
    cout << i << endl;
    Thread::sleep(10);
  }
}

void slowSimpleFn() {
  for (int i = 0; i < 100; ++i) {
    cout << i << endl;
    Thread::sleep(30);
  }
}

void mutexFn() {
  static volatile int k = 0;
  static Mutex mutex;
  mutex.lock();
  mutex.lock();
  for (int i = 0; i < 3; ++i) {
    cout << k + i << endl;
    Thread::sleep(1000);
  }
  k += 3;
  mutex.unlock();
  for (int i = 0; i < 3; ++i) {
    cout << k + i << endl;
    Thread::sleep(1000);
  }
  k += 3;
  mutex.unlock();
}

Mutex condMutex;
volatile int condVal = 0;
WaitCondition condTh;
WaitCondition condMain;

void condFn() {
  condMutex.lock();
  condMutex.lock();
  if (++condVal == 3) {
    cout << "condMain: signal" << endl;
    condMain.signal();
  }
  cout << "condTh: wait" << endl;
  condTh.wait(condMutex);
  cout << "condTh: continue" << endl;
  condMutex.unlock();
  condMutex.unlock();
}

Mutex lockMutex;
volatile int lockCnt = 0;

void lockerFn() {
  try {
    Locker locker(lockMutex);
    cout << "lockCnt = " << lockCnt++ << endl;
    Thread::sleep(1000);

    if (lockCnt % 2 == 0) {
      cout << "Throwing exception" << endl;
      throw Err("Artifical exception");
    }

    Locker locker2(locker);
    Locker locker3 = locker2;
    Locker locker4(locker2);
    locker4 = locker;
  }
  catch (Err err) {
    cout << err.msg() << endl;
  }
}

int main(int argc, char **argv) {
  
  cout << "Starting simple thread run test" << endl;
  try {
    Thread t1(static_cast<Thread::RunFn>(&simpleFn));
    Thread t2(static_cast<Thread::RunFn>(&slowSimpleFn));
    Thread t3(static_cast<Thread::RunFn>(&simpleFn));
    Thread::join(t1);
    Thread::join(t2);
    Thread::join(t3);
  }
  catch (Err err) {
    cout << err.msg() << endl;
    return 1;
  }
  Thread::sleep(2000);
  cout << "Finished simple thread run test" << endl;
  
  cout << "Starting simple thread run test2" << endl;
  try {
    Thread t1(static_cast<Thread::RunFn>(&simpleFn));
    Thread t2(static_cast<Thread::RunFn>(&slowSimpleFn));
    Thread t3(static_cast<Thread::RunFn>(&simpleFn));
  }
  catch (Err err) {
    cout << err.msg() << endl;
    return 1;
  }
  cout << "Waiting 10 seconds" << endl;
  Thread::sleep(10000);
  cout << "Finished simple thread run test2" << endl;

  cout << "Starting mutex test: should print one number per second" << endl;
  try {
    Thread t1(static_cast<Thread::RunFn>(&mutexFn));
    Thread t2(static_cast<Thread::RunFn>(&mutexFn));
    Thread t3(static_cast<Thread::RunFn>(&mutexFn));
    Thread::join(t1);
    Thread::join(t2);
    Thread::join(t3);
  }
  catch (Err err) {
    cout << err.msg() << endl;
    return 1;
  }
  cout << "Finished mutex test" << endl;
  
  cout << "Starting WaitCondition::signal test" << endl;

  try {
    condVal = 0;
    condMutex.lock();
    Thread t1(static_cast<Thread::RunFn>(&condFn));
    Thread t2(static_cast<Thread::RunFn>(&condFn));
    Thread t3(static_cast<Thread::RunFn>(&condFn));
    cout << "condMain: wait" << endl;
    condMain.wait(condMutex);
    cout << "condMain: continue" << endl;
    condMutex.unlock();

    Thread::sleep(1000);
    condMutex.lock();
    cout << "condTh: signal" << endl;
    condTh.signal();
    condMutex.unlock();

    Thread::sleep(1000);
    condMutex.lock();
    cout << "condTh: signal" << endl;
    condTh.signal();
    condMutex.unlock();

    Thread::sleep(1000);
    cout << "condTh: signal" << endl;
    condTh.signal();

    Thread::join(t1);
    Thread::join(t2);
    Thread::join(t3);
  }
  catch (Err err) {
    cout << err.msg() << endl;
    return 1;
  }

  cout << "Finished WaitCondition::signal test" << endl;

  cout << "Starting WaitCondition::signalAll test" << endl;

  try {
    condVal = 0;
    condMutex.lock();
    Thread t1(static_cast<Thread::RunFn>(&condFn));
    Thread t2(static_cast<Thread::RunFn>(&condFn));
    Thread t3(static_cast<Thread::RunFn>(&condFn));
    cout << "condMain: wait" << endl;
    condMain.wait(condMutex);
    cout << "condMain: continue" << endl;
    condMutex.unlock();

    Thread::sleep(1000);
    condMutex.lock();
    cout << "condTh: signalAll" << endl;
    condTh.signalAll();
    condMutex.unlock();

    Thread::join(t1);
    Thread::join(t2);
    Thread::join(t3);
  }
  catch (Err err) {
    cout << err.msg() << endl;
    return 1;
  }

  cout << "Finished WaitCondition::signalAll test" << endl;

  cout << "Starting Locker test" << endl;

    try {
    Thread t1(static_cast<Thread::RunFn>(&lockerFn));
    Thread t2(static_cast<Thread::RunFn>(&lockerFn));
    Thread t3(static_cast<Thread::RunFn>(&lockerFn));
    Thread t4(static_cast<Thread::RunFn>(&lockerFn));
    Thread t5(static_cast<Thread::RunFn>(&lockerFn));
    Thread t6(static_cast<Thread::RunFn>(&lockerFn));
    Thread::join(t1);
    Thread::join(t2);
    Thread::join(t3);
    Thread::join(t4);
    Thread::join(t5);
    Thread::join(t6);
  }
  catch (Err err) {
    cout << err.msg() << endl;
    return 1;
  }


  cout << "Finished Locker test" << endl;

	return 0;
}
